
package com.kweative.lingua.server.protocol;

import com.kweative.format.JsonFormat;
import com.kweative.lingua.server.Application;
import com.kweative.lingua.server.Config;
import com.kweative.lingua.server.exceptions.InvalidRequestException;
import com.kweative.lingua.server.structures.AppRequest;
import com.kweative.lingua.server.exceptions.NotFoundException;
import com.kweative.lingua.server.request.HttpRequestHandler;
import com.kweative.lingua.server.request.RequestHandler;
import com.kweative.lingua.server.response.ResponseHandler;
import com.kweative.lingua.server.structures.Arguments;
import com.kweative.lingua.server.structures.ArgumentsNamed;
import com.kweative.network.HttpCookie;
import com.kweative.network.HttpRequest;
import com.kweative.network.HttpResponse;
import com.kweative.network.Response;
import com.kweative.type.Node;
import com.kweative.type.Node.Type;
import com.kweative.util.ArrayUtil;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Date;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author henrik
 */
public class ExtDirectProtocol extends Protocol {
    private final String reserved[] = {"action","method","data","type","tid","extAction","extMethod","extTID","extUpload"};
    
    @Override
    public boolean canRead(RequestHandler request) {
        if (request.getFormat() instanceof JsonFormat) {
            Node d = request.getData();
            if (d == null) return false;
            Node elm = d;

            if (d.isArray())
                elm = d.children().getFirst();

            if (elm.hasAttribute("action") &&
                    elm.hasAttribute("method") &&
                    elm.hasAttribute("type") &&
                    elm.hasAttribute("tid"))
                    return true;
            if (elm.hasAttribute("extAction") &&
                    elm.hasAttribute("extMethod") &&
                    elm.hasAttribute("extTID"))
                    return true;
        }
        return false;
    }

    @Override
    public AppRequest[] read(RequestHandler request) throws NotFoundException {
        Node d = request.getData();
        Stack<AppRequest> requests = new Stack<AppRequest>();
        if (d.isArray()) {
            for(Node child:d.children()) {
                requests.add(this.readSingle(child,request));
            }
        } else {
            requests.add(this.readSingle(d,request));
            
        }
        return requests.toArray(new AppRequest[0]);
    }

    public AppRequest readSingle(Node parent,RequestHandler request) throws NotFoundException {
        AppRequest ar = new AppRequest();
        String controllerName = "";
        String methodName = "";
        if (request instanceof HttpRequest) {
            URL url = ((HttpRequest)request).getUrl();
            ar.setSID(((HttpRequest) request).getCookie(getSessionIdName()));
            String parts[] = url.getPath().split("/");
            if (parts.length == 2) {
                ar.setApplicationName(parts[1]);
            } else
                throw new NotFoundException("Invalid path: Correct syntax /appname/!");
        } else {
            throw new NotFoundException("Ext.Direct requires HTTP transport/!");
        }
        
        
        Arguments arguments = null;
        if (parent.hasAttribute("extAction")) {
            arguments = new ArgumentsNamed();
            controllerName = parent.getAttribute("extAction").toString();
            methodName = parent.getAttribute("extMethod").toString();
            ar.setTransationId(parent.getAttribute("extTID").toString());
            if (parent.hasChildren()) {
                for(Node child:parent.children()) {
                    if (!isReserved(child.getName(),request.getResponseProtocol())) {
                        ((ArgumentsNamed)arguments).add(child.getName(),child);
                    }
                }
            }
        } else {
            arguments = new Arguments();
            controllerName = parent.getAttribute("action").toString();
            methodName = parent.getAttribute("method").toString();
            ar.setTransationId(parent.getAttribute("tid").toString());
            if (parent.hasChild("data")) {
                for(Node child:parent.getChild("data").children()) {
                   arguments.add(child);
                }
            }
        }
        ar.setControllerName(controllerName);
        ar.setMethodName(methodName);
        ar.setArguments(arguments);
        return ar;
    }

    

    @Override
    public Node normalizeResponse(AppRequest appRequest, Node result) {
        Node root = new Node(Type.COMPLEX);
        root.setAttribute("SID",appRequest.getSID());
        root.setAttribute("type","rpc");
        root.setAttribute("action",appRequest.getControllerName());
        root.setAttribute("method",appRequest.getMethodName());
        if (appRequest.getTransactionId() != null)
            root.setAttribute("tid",Integer.valueOf(appRequest.getTransactionId()));
        else
            root.setAttribute("tid",-1);

        root.addChild(result.setName("result"));
        
        return root;
    }

    @Override
    public Node normalizeDocumentation(AppRequest appRequest, Node result) throws InvalidRequestException {
        Node root = new Node(result.getAttribute("name").toString(),Type.COMPLEX);
        URL url = null;
        if (appRequest.getRequestHandler() instanceof HttpRequestHandler) {
            url = ((HttpRequestHandler)appRequest.getRequestHandler()).getUrl();
        } else
            throw new InvalidRequestException("Ext.Direct only support HTTP");

        String basePath;
        
        basePath = String.format("%s://%s:%s/%s/", url.getProtocol(), url.getHost(), url.getPort(), result.getAttribute("name").toString());
        
        root.setAttribute("url", basePath);
        root.setAttribute("type","remoting");
        Node actions = new Node("actions",Type.COMPLEX);
        if (result.getChild("controllers") != null) {
            for(Node ctrlNode:result.getChild("controllers").children()) {
                Node ctrl = new Node(ctrlNode.getAttribute("name").toString(),Type.ARRAY);
                if (ctrlNode.getChild("methods") == null) continue;
                for(Node methodNode:ctrlNode.getChild("methods").children()) {
                    Node method = new Node(Type.COMPLEX);
                    method.setAttribute("name",methodNode.getAttribute("name"));
                    if (methodNode.getChild("parms") == null)
                        method.setAttribute("len",0);
                    else
                        method.setAttribute("len",methodNode.getChild("parms").children().size());
                    ctrl.addChild(method);
                }
                actions.addChild(ctrl);
            }
        }
        root.addChild(actions);
        return root;
    }
    @Override
    public void write(ResponseHandler response, Node result) {
        try {
            
            if (response.getSession() != null) {
                HttpCookie cookie = new HttpCookie(getSessionIdName(), response.getSession().getSID());
                Date d = new Date();
                cookie.setExpires(new Date(d.getTime() + getSessionExpire()));
                ((HttpResponse)response.getResponse()).addCookie(cookie);
            }
            JsonFormat format = new JsonFormat();
            String content = format.serialize(result);
            response.headers().put("Content-Type","application/json");
            response.addBody(content);
        } catch (Exception ex) {
            response.setStatus(Response.Status.E_INTERNAL);
            response.setBody("Could not properly format output");
            Logger.getLogger(ExtDirectProtocol.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    @Override
    public void writeException(ResponseHandler response, Throwable ex) {
        try {
            JsonFormat format = new JsonFormat();
            Node error = new Node(Type.COMPLEX);
            error.setAttribute("type","exception");
            error.setAttribute("message", ex.getMessage());
            error.setAttribute("where", ArrayUtil.join(ex.getStackTrace(), "\n"));
            write(response, error);
        } catch (Exception ex1) {
            Logger.getLogger(XmlSimpleProtocol.class.getName()).log(Level.SEVERE, null, ex1);
        }
    }

    @Override
    public void writeDocumentation(ResponseHandler response, Node result) {
        try {
            JsonFormat format = new JsonFormat();
            String content = format.serialize(result);
            response.headers().put("Content-Type","text/javascript");
            response.addBody(String.format("Ext.Direct.addProvider(%s);",content));
        } catch (Exception ex) {
            response.setStatus(Response.Status.E_INTERNAL);
            response.setBody("Could not properly format output");
            Logger.getLogger(ExtDirectProtocol.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

}
